<?php
/**
 *
 *  @todo default action derived from module name
 *  @todo element classes must be inheritable
 *
 */
abstract class Yz_Form_Abstract
    extends Yz_Object
    implements Yz_Form_Interface
{

    /** example custom class settings:
     *  @const string
     */
    #const RENDERER_TABLE = 'Yz_Form_Renderer_Table';
    #const ELEMENT_CUTE = '%{NS}\\Form_%{TYPE}_Very%{NAME}';

    /**
     *  @var string
     */
    protected $name;
    /**
     *  @var string
     */
    protected $action = "";
    /**
     *  @var string
     */
    protected $method = 'POST';


    /** The form's elements
     *  @var array
     */
    protected $elements = array();

    /** The storage for assigned variables
     *  @var array
     */
    protected $values;

    /**
     *  @var array
     */
    protected $parseErrors = array();



    /****************************
     *      Public methods      *
     ****************************/


    /**
     *
     */
    public function __construct( $config )
    {
        debug();
        $this->name = $this->shortClassName();
        $this->assignElements( $config );
    }


    /**
     *  @param array-type elements: [name: params]
     *      Params are passed to element constructor
     *  @return chainable
     */
    public function assignElements( $elements )
    {
        debug();
        $elements = Yz_Hash::valueOf($elements);
        foreach ($elements as $name => $params) {
            $el = $this->createElement($name, $params);
            if (null !== $el) {
                $this->elements[$name] = $el;
            }
        }
        return $this;
    }

    /**
     *  @return string rendered form
     */
    final public function __toString()
    {
        return "Yz_Form('{$this->name}')";

        // ### old ###
        try {
            return $this->render();
        } catch (Exception $ex) {
            debug($ex->getTraceAsString());
            return "$ex";
        }
    }

    /**
     *  Also a getter may be used: x = $form->name
     *
     */
    final public function getName()
    {
        return $this->name;
    }

    /**
     *  Also a setter may be used: $form->name
     *
     */
    final public function setName($name)
    {
        $this->name = Yz_String::test($name);
        return $this;
    }

    /**
     *  Also a getter may be used: x = $form->action
     *
     */
    final public function getAction()
    {
        return $this->action;
    }

    /**
     *  Also a setter may be used: $form->action
     *
     */
    final public function setAction($action)
    {
        $this->action = Yz_String::test($action);
        return $this;
    }

    /**
     *  Also a getter may be used: x = $form->action
     *
     */
    final public function getMethod()
    {
        return $this->method;
    }

    /**
     *  Also a setter may be used: $form->action
     *
     */
    final public function setMethod($method)
    {
        $this->method = Yz_String::test($method);
        return $this;
    }

    /**
     *  return copy of elements array
     *  @return Yz_Hash
     */
    final public function getElements()
    {
        return new Yz_Hash($this->elements);
    }

    /**
     *  Also a getter may be used: x = $form->values
     *  @return array
     */
    final public function getValues()
    {
        debug();
        $values = array();
        foreach ($this->elements as $elName => $el) {
            $values[$elName] = $el->getValue();
        }
        return $values;
    }

    /**
     *  Also a setter may be used: $form->values = x
     *
     */
    public function assignValues($values)
    {
        debug();
        $values = Yz_Hash::valueOf($values);
        foreach ($this->elements as $elName => $el) {
            $el->assignValueFromArray($values);
        }
        return $this;
    }

    /**
     *  A renderer defines:
     *  - the visual wrapping ("design") of form elements
     *  - the order of elements
     *  - the relationships among elements (eg. If A=x, skip B)
     *  - the SUBMIT element
     */
    public function render(Yz_Template_Interface $template)
    {
        debug();
        $class = $this->identifyClass('Renderer', 'Default');
        $r = new $class($this);
        return $r->render($template);
    }


    /**
     *  A parser defines:
     *  - the "requiredness" of elements
     *  - the relationships among elements (eg. If A=x, skip B)
     *  - external parameters which affect the parsing (resubmission, delays, etc)
     */
    public function parse($values)
    {
        debug();
        $class = $this->identifyClass('Parser', 'Default');
        $p = new $class($this);
        // TODO collect values and errors from elements
        $res = $p->parse($this, $values);
        if (false === $res) {
            $this->parseErrors = $p->getErrors();
        }
        return $this;
    }



    /****************************
     *      Protected methods   *
     ****************************/

    /**
     *
     *
     */
    protected function createElement($name, $fobj)
    {
        $type = 'input';
        if ($fobj instanceof Yz_Class) {
            $type = $fobj->formElementType;
            if (false === $type) {
                return null;
            }
        } else {
            $fobj = null;
        }

        $fclass = $this->identifyClass('Element', $type);
        debug("Creating element '$name' type '$fclass'");
        return new $fclass($this, $name, $fobj);
    }


    protected function identifyClass($libType, $libName)
    {
        $libName = Yz_String::toCamelCase($libName, true);
        $const = $this->className() . '::' . strtoupper($libType . '_' . $libName);
        if (defined($const)) {
            $class = constant($const);
            $class = strtr($class, array(
                '%{NS}' => $this->namespaceName(),
                '%{TYPE}' => $libType,
                '%{NAME}' => $libName,
                ))
            ;
        } else {
            $class = "Yz_Form_{$libType}_{$libName}";
        }
        Yz::loadClass($class);
        return $class;
    }

}
